<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */

namespace lflow\core\services;

use lflow\core\BaseServices;
use lflow\dao\ProcessDao;
use lflow\exceptions\WorkFlowException;
use lflow\lib\util\AssertHelper;
use lflow\lib\util\Logger;
use lflow\model\ProcessModel;
use lflow\parser\CpktParser;
use lflow\parser\ModelParser;

class ProcessServices extends BaseServices
{

    /**
     * @param \lflow\dao\ProcessDao $dao
     */
    public function __construct(ProcessDao $dao)
    {
        $this->dao = $dao;
    }

    /**
     * @param \lflow\model\ProcessModel|null $process
     * @param string                         $idOrName
     */
    public function check(?ProcessModel $process, string $idOrName): void
    {
        AssertHelper::notNull($process, "指定的流程定义[id/name=" . $idOrName . "]不存在");
        if ((int)$process->getData('state') === 0) {
            throw new WorkFlowException("指定的流程定义[id/name=" . $idOrName .
                ",version=" . $process->getData('version') . "]为非活动状态");
        }
    }

    /**
     * 根据流程定义Json的输入流保存至数据库中
     *
     * @param array  $input
     * @param string $creator
     *
     * @return string
     */
    public function deploy(array $input, string $creator): string
    {
        try {
            AssertHelper::notNull($input);
            AssertHelper::notNull($input['name'] ?? '');
            AssertHelper::notNull($creator ?? '');
            $process = $this->dao->get(['name' => $input['name']], ['*'], [], 'version desc');
            $version = 1;
            if (!empty($process)) {
                $version = $process->getData('version') + 1;
            }
            $data   = [
                'type'         => $input['type'] ?? '',
                'name'         => $input['name'] ?? '',
                'display_name' => $input['display_name'] ?? '',
                'instance_url' => $input['instance_url'] ?? '',
                'state'        => $input['state'] ?? 0,
                'content'      => (object)$input ?? (object)[],
                'version'      => $version,
                'creator'      => $creator,
            ];
            $deploy = $this->dao->save($data);
            $this->cacheDriver()->clear();
            return $deploy->getData('id');
        } catch (\Exception $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 根据流程定义id、json的输入流保存至数据库
     *
     * @param string $id
     * @param array  $input
     *
     * @return mixed
     */
    public function redeploy(string $id, array $input): string
    {
        try {
            AssertHelper::notNull($input);
            AssertHelper::notNull($input['name'] ?? '');
            $process = $this->dao->get(['id' => $id]);
            AssertHelper::notNull($process, '未知流程或已被删除请确认后重试');
            $process->content = (object)$input;
            $process->save();
            $this->cacheDriver()->clear();
            return $process->getData('id');
        } catch (\Exception $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 根据processId卸载流程
     *
     * @param string $id
     */
    public function undeploy(string $id): void
    {
        try {
            AssertHelper::notNull($id, '缺失ID参数');
            $entity = $this->dao->get(['id' => $id]);
            AssertHelper::notNull($entity, '未知流程或已被删除请确认后重试');
            $entity->state = self::STATE_FINISH;
            $entity->save();
            $this->cacheDriver()->clear();
        } catch (\Exception $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 查询流程定义
     *
     * @param array $where
     *
     * @return array
     * @throws \ReflectionException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getProcess(array $where, $isPage = false): array
    {
        [$page, $limit] = $this->getPageValue($isPage);
        $list  = $this->dao->selectList($where, '*', $page, $limit, 'name', [], true);
        $count = $this->dao->count($where);
        return compact('list', 'count');
    }

    /**
     * 级联删除指定流程定义的所有数据
     *
     * @param string $id
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function cascadeRemove(string $id): void
    {
        AssertHelper::notNull($id, '缺少ID参数');
        $entity = $this->dao->get(['id' => $id]);

        $HistoryOrders = app()->make(HistoryOrderServices::class);
        $historyOrders = $HistoryOrders->dao->selectList(['process_id' => $id]);
        foreach ($historyOrders as $historyOrder) {
            //task

        }
        $entity->delete();
        $this->cacheDriver()->clear();
    }

    /**
     * 更新流程状态
     *
     * @param string     $id
     * @param string|int $state
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function updateState(string $id, string|int $state = 0): void
    {
        AssertHelper::notNull($id, '缺少ID参数');
        $entity        = $this->dao->get(['id' => $id]);
        $entity->state = $state;
        $entity->save();
        $this->cacheDriver()->clear();
    }

    /**
     * @param string $id
     *
     * @return \lflow\model\ProcessModel
     */
    public function getProcessById(string $id): ProcessModel
    {
        AssertHelper::notEmpty($id);
        $entity = $this->cacheDriver()->remember(md5($id), function () use ($id) {
            $entity = $this->dao->get($id);
            AssertHelper::notNull($entity, '流程不不存在或被删除');
            $entity->set('process_ckpt', CpktParser::parse($entity));
            return $entity;
        });
        if (!$this->cacheDriver()->has(md5($id))) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[id={}] from cache." . md5($id));
            }
        }

        if (!empty($entity)) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[id={}] from cache." . $id);
            }
        }
        return $entity;
    }

    /**
     * @param string $name
     *
     * @return mixed
     */
    public function getLatestProcessVersion(string $name): int
    {
        AssertHelper::notEmpty($name);
        $where = ['name' => $name];
        return $this->dao->getMax($where, 'version');
    }

    /**
     *
     * @param string   $name 流程名称
     * @param int|null $version 流程版本
     *
     * @return \lflow\model\ProcessModel
     */
    public function getProcessByVersion(string $name, int|null $version): ProcessModel
    {
        AssertHelper::notEmpty($name);
        if ($version == null) {
            $version = $this->getLatestProcessVersion($name);
        }
        if ($version == null) {
            $version = 1;
        }
        $processName = $name . '.' . $version;
        $entity      = $this->cacheDriver()->remember(md5($processName), function () use ($name, $version) {
            $where  = ['name' => $name, 'version' => $version];
            $entity = $this->dao->get($where);
            AssertHelper::notNull($entity, '流程不不存在或被删除');
            $entity->set('process_ckpt', CpktParser::parse($entity));
            return $entity;
        });
        if (!$this->cacheDriver()->has(md5($processName))) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[id={}] from cache." . md5($processName));
            }
        }

        if (!empty($entity)) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[process_name={}] from cache." . $processName);
            }
        }
        return $entity;
    }

}
